<?php
if (!defined('APP_ID')) { die('No direct script access allowed'); }

/**
 * Router class
 */
class Router {
	private static $_instance = null;
	private $_language = '';
	private $_controller = '';
	private $_action = '';
	private $_default_language = '';
	private $_default_controller = '';
	private $_default_action = '';
	private $_route_string = '';
	private $_route_array = array();
	private $_base_url = '';
	private $_current_url = '';

	public static function &instance() {
		if (!isset(self::$_instance)) {
			$c = __CLASS__;
			self::$_instance = new $c;
		}

		return self::$_instance;
	}

	public function __clone() {
		throw new Node_Exception('Can`t clone singletone object');
	}

    /**
	 * Constructor
	 */
	private function __construct() {
		define('UPLOAD_URL', Config::get('upload_url'));
		define('PUBLIC_URL', Config::get('public_url'));

		$path_info = Input::server('PATH_INFO');
		$router_variable = Input::get(Config::get('variable', 'router'));
		if (!empty($path_info)) {
			$this->_route_string = $path_info;
		} else if (!empty($router_variable)) {
			$this->_route_string = $router_variable;
		}

		if (Input::isCli()) {
			$this->_default_language = self::fixRoute(Config::get('default', 'i18n'));
			$this->_default_controller = self::fixRoute(Config::get('controller_cli', 'router'));
			$this->_default_action = self::fixRoute(Config::get('action_cli', 'router'));
		} else {
			$this->_default_language = self::fixRoute(Config::get('default', 'i18n'));
			$this->_default_controller = self::fixRoute(Config::get('controller', 'router'));
			$this->_default_action = self::fixRoute(Config::get('action', 'router'));
		}
		$this->_language = $this->_default_language;
		$this->_controller = $this->_default_controller;
		$this->_action = $this->_default_action;
	}

	public function init() {
		if (Input::isCli()) {
			$this->_parseRouteCli();
		} else {
			$this->_parseRoute();
		}

		return $this;
	}

	/**
	 * Parsing route string
	 */
	public function startup() {
		Hooks::execute('startup');

		if (!Input::isPostBack() && Config::get('enabled', 'cache', false) && Config::get('output', 'cache', false)) {
			$output = Cache::get(self::getUniqueKey());
			if ($output === null) {
				Config::set('perform_output_caching', true, 'cache');
				$this->_execute();
			} else {
				Output::body($output);
			}
		} else {
			$this->_execute();
		}

		return $this;
	}

	/**
	 * Execute system
	 */
	private function _execute() {
		// Call before method if exists
		if (method_exists(Controller::instance(), '__before')) {
			Controller::instance()->__before();
		}

		// Call controller action and send rest of variables
		call_user_func_array(
			array(Controller::instance(), $this->_getActionMethod()),
			$this->_route_array
		);

		// Call after method  if exists
		if (method_exists(Controller::instance(), '__after')) {
			Controller::instance()->__after();
		}
	}

	/**
	 * Parsing route string
	 */
	private function _parseRoute() {
		// Split route string into route array
		$pattern = '/([^\/\?]+)/';
		if (preg_match_all($pattern, $this->_route_string, $matches)) {
			foreach ($matches[1] as $match) {
				$match = trim($match);
				if (!strlen($match)) { continue; }
				$this->_route_array[] = $match;
			}
		}

		// Search for language in route
		if (!empty($this->_route_array)) {
			$languages = Config::get('list', 'i18n');
			foreach ($languages as $language) {
				if ($this->_checkRoute($language, $this->_route_array[0])) {
					$this->_language = $this->fixRoute(array_shift($this->_route_array));
					break;
				}
			}
		}

		// Search for controller in route
		$controller_str = '';
		if (!empty($this->_route_array)) {
			$controller_str = $this->fixRoute($this->_route_array[0]);
		}
		$controller = Controller::find($controller_str, $this->_controller);
		if (!$controller['default']) {
			$this->_controller = $controller_str;
			array_shift($this->_route_array);
		}

		// If controller not found throw exception
		if (!$controller['found']) {
			throw new Node_Exception('Controller "' . $controller['name'] . '" not found.');
		}

		// Search for action in route
		if (!empty($this->_route_array)) {
			$action_name = $this->fixRoute($this->_route_array[0]);
			if (method_exists($controller['name'], $this->_formatActionMethod($action_name))) {
				$this->_action = $action_name;
				array_shift($this->_route_array);
			}
		}

		// If action not found throw exception
		if (!method_exists($controller['name'], $this->_getActionMethod())) {
			$action_str = $controller['name'] . '->' . $this->_getActionMethod();
			throw new Node_Exception('Action "' . $action_str . '" not found.');
		}

		$this->_buildUrl();
	}

	/**
	 * Parsing route string (cli)
	 */
	private function _parseRouteCli() {
		$argc_start = 1;

		// Search for language in route
		if ($_SERVER['argc'] > $argc_start) {
			$languages = Config::get('list', 'i18n');
			foreach ($languages as $language) {
				if ($this->_checkRoute($language, $_SERVER['argv'][$argc_start])) {
					$this->_language = $this->fixRoute($_SERVER['argv'][$argc_start]);
					$argc_start++;
					break;
				}
			}
		}

		// Search for controller in route
		$controller_str = '';
		if ($_SERVER['argc'] > $argc_start) {
			$controller_str = $this->fixRoute($_SERVER['argv'][$argc_start]);
		}
		$controller = Controller::find($controller_str, $this->_controller);
		if (!$controller['default']) {
			$this->_controller = $controller_str;
			$argc_start++;
		}

		// If controller not found throw exception
		if (!$controller['found']) {
			throw new Node_Exception('Controller "' . $controller['name'] . '" not found.');
		}

		// Search for action in route
		if ($_SERVER['argc'] > $argc_start) {
			$action_name = $this->fixRoute($_SERVER['argv'][$argc_start]);
			if (method_exists($controller['name'], $this->_formatActionMethod($action_name))) {
				$this->_action = $action_name;
				$argc_start++;
			}
		}

		// If action not found show error and throw exception
		if (!method_exists($controller['name'], $this->_getActionMethod())) {
			$action_str = $controller['name'] . '->' . $this->_getActionMethod();
			throw new Node_Exception('Action "' . $action_str . '" not found.');
		}

		for ($i = $argc_start; $i < $_SERVER['argc']; $i++) {
			$this->_route_array[] =  $_SERVER['argv'][$i];
		}

		$this->_buildUrl();
	}

	private function _buildUrl() {
		// Building base url string
		$this->_base_url = PUBLIC_URL;
		if (!Config::get('rewrite_engine', 'router')) {
			$this->_base_url .= Config::get('index_file', 'router');
			if (!Config::get('path_info', 'router')) {
				$this->_base_url .= '?' . Config::get('variable', 'router') . '=';
			}
			$this->_base_url .= '/';
		}

		if (count(Config::get('list', 'i18n')) > 1) {
			if ($this->_language != $this->_default_language) {
				$this->_base_url .= $this->fixUrl($this->_language) . '/';
			}
		}

		define('BASE_URL', $this->_base_url);
		Config::set('base_url', $this->_base_url);


		// Building current url string
		$this->_current_url = BASE_URL;
		if ($this->_controller != $this->_default_controller) {
			$this->_current_url .= $this->fixUrl($this->_controller) . '/';
		}

		if ($this->_action != $this->_default_action) {
			$this->_current_url .= $this->fixUrl($this->_action) . '/';
		}

		define('CURRENT_URL', $this->_current_url);
		Config::set('current_url', $this->_current_url);
	}

	/**
	 * Return current language
	 */
	public static function getLanguage() {
		return self::instance()->_language;
	}

	/**
	 * Return current module
	 */
	public static function getController() {
		return self::instance()->_controller;
	}

	/**
	 * Return current page
	 */
	public static function getAction() {
		return self::instance()->_action;
	}

	/**
	 * Return route array
	 */
	public static function getRoute() {
		return self::instance()->_route_array;
	}

	/**
	 * Return unique key for route
	 */
	public static function getUniqueKey () {
		$router = self::instance();
		$key = $router->_language . '_';
		$key .= $router->_controller . '_';
		$key .= $router->_action;
		if (!empty($router->_route_array)) {
			$key .= '_' . md5(serialize($router->_route_array));
		}

		return $key;
	}

	/**
	 * Compare two route strings
	 */
	private function _checkRoute($str_1, $str_2) {
		$str_1 = self::fixRoute($str_1);
		$str_2 = self::fixRoute($str_2);
		if ($str_1 == $str_2) {
			return true;
		}
		return false;
	}

	/**
	 * Fix route string
	 */
	public static function fixRoute($str) {
		$result = trim(strtolower($str));
		$result = preg_replace('/[\-\+\s]+/', '_', $result);
		return $result;
	}

	/**
	 * Fix url string
	 */
	public static function fixUrl($str) {
		$result = trim(strtolower($str));
		$result = preg_replace('/[\_\s]+/', '-', $result);
		return $result;
	}

	/**
	 * Return action method name
	 */
	private function _getActionMethod($action = '') {
		$action = empty($action) ? $this->_action : $action;
		return $this->_formatActionMethod($action);
	}

	/**
	 * Return action method name
	 */
	private function _formatActionMethod($action) {
		$result = Config::get('action_prefix', 'router') . $action;
		$result .= Config::get('action_suffix', 'router');
		return $result;
	}

	/**
	 * Checks route
	 */
	public static function checkRoute($controller, $action) {
		$result = false;

		if ((self::getController() == self::fixRoute($controller))
			&& (self::getAction() == self::fixRoute($action))) {
			$result = true;
		}

		return $result;
	}

	/**
	 * Get current path
	 */
	public static function getCurrentPath() {
		$result = self::getController() . '/';
		$result .= self::getAction() . '/';
		$result .= implode('/', self::getRoute());
		return $result;
	}
}
